﻿using System;
using CmdLine;
using FLib;
using LibraryGuard.Interfaces;
using LibraryGuard.Interfaces.Configuration;
using Cfg = LibraryGuard.Configuration;

namespace LibraryGuard
{
	/// <summary>
	/// Config loader is responsible for parsing command line, loading config and creating guards from it.
	/// </summary>
	public sealed class ConfigLoader
	{
		#region Logger
		private static NLog.Logger Logger = NLog.LogManager.GetLogger("ConfigLoader");
		#endregion

		#region Private fields
		private Arguments Args = null;
		private Cfg.Configuration Config = null;
		#endregion

		#region Public Properties
		/// <summary>
		/// Loaded configuration.
		/// </summary>
		public IConfiguration Configuration
		{
			get { return this.Config; }
		}

		/// <summary>
		/// Created guards.
		/// </summary>
		public IGuard[] Guards { get; private set; }

		/// <summary>
		/// Set to true if program is run as a service.
		/// </summary>
		public bool Service
		{
			get { return this.Args.Service; }
		}
		#endregion

		#region Methods
		/// <summary>
		/// Parses command line arguments.
		/// </summary>
		/// <returns></returns>
		public bool ParseArguments()
		{
			this.LoadArguments();
			return this.Args != null;
		}

		/// <summary>
		/// Runs loading.
		/// </summary>
		/// <returns></returns>
		public bool Run(IActionList actionList)
		{
			Validate.Debug(() => this.Args, v => v.NotNull());

			this.Config = null;
			this.Guards = null;

			if (this.Args.SaveConfig)
			{
				this.SaveEmpty();
				return false;
			}

			this.LoadConfig();
			if (this.Config == null)
				return false;

			this.Guards = Cfg.Utilities.CreateGuards(this.Config, this.OnInvalidGuard);
			foreach (var guard in this.Guards)
				guard.ActionList = actionList;

			Logger.Debug("Search directories:");
			foreach (var item in this.Configuration.Directories)
				Logger.Debug("\t" + item);
			Logger.Debug("Extensions: {0}", string.Join(", ", this.Configuration.Extensions));

			Logger.Debug("Enabled guards:");
			foreach (var item in this.Guards)
				Logger.Debug("\t" + item.GetType().Name);

			if (this.Configuration.Continuous)
				Logger.Info("Continuous mode enabled");
			Logger.Info("Execution mode: " + this.Configuration.Mode);

			if (this.Config.DueTime != TimeSpan.Zero)
				Logger.Info("Work will be scheduled with intervals of {0} for {1}", this.Config.DueTime, this.Config.WorkingTime);

			return true;
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Loads arguments and checks for correctness.
		/// </summary>
		/// <returns></returns>
		private void LoadArguments()
		{
			Arguments args = null;
			try
			{
				args = CmdLine.CommandLine.Parse<Arguments>();
			}
			catch (CmdLine.CommandLineHelpException e)
			{
				Console.WriteLine(e.ArgumentHelp.GetHelpText(Console.BufferWidth));
				Logger.Info("Help shown, exiting");
				return;
			}
			catch (CmdLine.CommandLineException e)
			{
				Console.WriteLine(e.ArgumentHelp.Message);
				Console.WriteLine(e.ArgumentHelp.GetHelpText(Console.BufferWidth));
				Logger.Fatal("Invalid parameters");
				return;
			}

			if (string.IsNullOrWhiteSpace(args.ConfigFile))
			{
				Console.WriteLine("Config file must be specified");
				Logger.Fatal("Invalid parameters: config file must be specified");
				return;
			}
			if (args.SaveConfig && args.Service)
			{
				Logger.Fatal("Cannot save empty config file when running as a service");
				return;
			}
			this.Args = args;
		}

		/// <summary>
		/// Saves empty config file.
		/// </summary>
		private void SaveEmpty()
		{
			try
			{
				Cfg.Utilities.SaveEmpty(this.Args.ConfigFile);
			}
			catch (Exception ex)
			{
				Console.WriteLine("Cannot save config file, see log for more details");
				Logger.FatalException("Cannot save config file", ex);
				return;
			}
			Console.WriteLine("Config file saved");
			Logger.Info("Config file saved");
		}

		/// <summary>
		/// Loads configuration and checks for correctness.
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		private void LoadConfig()
		{
			Configuration.Configuration config = null;
			try
			{
				config = Cfg.Utilities.LoadConfiguration(this.Args.ConfigFile);
			}
			catch (TypeInitializationException ex)
			{
				Logger.FatalException("Cannot load config file", ex.InnerException);
				return;
			}
			catch (Exception ex)
			{
				Logger.FatalException("Cannot load config file", ex);
				return;
			}

			if (!string.IsNullOrWhiteSpace(config.Advanced.LoggerConfig))
			{
				try
				{
					NLog.LogManager.Configuration = new NLog.Config.XmlLoggingConfiguration(config.Advanced.LoggerConfig);
					NLog.LogManager.ReconfigExistingLoggers();
					Logger.Info("Logger configuration reloaded, program started");
				}
				catch (Exception ex)
				{
					Logger.Fatal("Cannot change logger configuration", ex);
					return;
				}
			}

			if (!Cfg.Utilities.CheckCorrectness(config))
			{
				Console.WriteLine("Config file is incorrect: missing directories and/or extensions");
				Logger.Fatal("Config file is incorrect: missing directories and/or extensions");
				return;
			}

			if (!Cfg.Utilities.ConvertDirectories(config))
			{
				Logger.Fatal("One or more directories are inaccessible");
				return;
			}
			config.Advanced.Service = this.Args.Service;
			this.Config = config;
		}

		/// <summary>
		/// Callback for guard creation error.
		/// </summary>
		/// <param name="name"></param>
		/// <param name="ex"></param>
		private void OnInvalidGuard(string name, Exception ex)
		{
			Logger.ErrorException("Cannot load {0}".FormatWith(name), ex);
		}
		#endregion

		#region CmdLine argumets
		[CommandLineArguments(Program = "LibraryGuard.exe")]
		private sealed class Arguments
		{
			[CommandLineParameter(Name = "Help", Command = "?", Required = false, Default = false, Description = "Shows help", IsHelp = true)]
			public bool Help { get; set; }

			[CommandLineParameter(Name = "Service", Command = "Service", Required = false, Default = false, Description = "Process is run as service. Internall property.")]
			public bool Service { get; set; }

			[CommandLineParameter(Name = "ConfigFile", Command = "ConfigFile", Required = true, Description = "File with the configuration")]
			public string ConfigFile { get; set; }

			[CommandLineParameter(Name = "SaveConfig", Command = "SaveConfig", Required = false, Description = "If set, config will be saved to ConfigFile and then program will exit")]
			public bool SaveConfig { get; set; }
		}
		#endregion
	}
}
